/*
 * Copyright (c) 2000, 2007, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.imageio.stream;

import java.io.IOException;
import java.io.UTFDataFormatException;
import java.nio.ByteOrder;

/**
 * An abstract class implementing the <code>ImageOutputStream</code> interface.
 * This class is designed to reduce the number of methods that must
 * be implemented by subclasses.
 */
public abstract class ImageOutputStreamImpl
    extends ImageInputStreamImpl
    implements ImageOutputStream {

  /**
   * Constructs an <code>ImageOutputStreamImpl</code>.
   */
  public ImageOutputStreamImpl() {
  }

  public abstract void write(int b) throws IOException;

  public void write(byte b[]) throws IOException {
    write(b, 0, b.length);
  }

  public abstract void write(byte b[], int off, int len) throws IOException;

  public void writeBoolean(boolean v) throws IOException {
    write(v ? 1 : 0);
  }

  public void writeByte(int v) throws IOException {
    write(v);
  }

  public void writeShort(int v) throws IOException {
    if (byteOrder == ByteOrder.BIG_ENDIAN) {
      byteBuf[0] = (byte) (v >>> 8);
      byteBuf[1] = (byte) (v >>> 0);
    } else {
      byteBuf[0] = (byte) (v >>> 0);
      byteBuf[1] = (byte) (v >>> 8);
    }
    write(byteBuf, 0, 2);
  }

  public void writeChar(int v) throws IOException {
    writeShort(v);
  }

  public void writeInt(int v) throws IOException {
    if (byteOrder == ByteOrder.BIG_ENDIAN) {
      byteBuf[0] = (byte) (v >>> 24);
      byteBuf[1] = (byte) (v >>> 16);
      byteBuf[2] = (byte) (v >>> 8);
      byteBuf[3] = (byte) (v >>> 0);
    } else {
      byteBuf[0] = (byte) (v >>> 0);
      byteBuf[1] = (byte) (v >>> 8);
      byteBuf[2] = (byte) (v >>> 16);
      byteBuf[3] = (byte) (v >>> 24);
    }
    write(byteBuf, 0, 4);
  }

  public void writeLong(long v) throws IOException {
    if (byteOrder == ByteOrder.BIG_ENDIAN) {
      byteBuf[0] = (byte) (v >>> 56);
      byteBuf[1] = (byte) (v >>> 48);
      byteBuf[2] = (byte) (v >>> 40);
      byteBuf[3] = (byte) (v >>> 32);
      byteBuf[4] = (byte) (v >>> 24);
      byteBuf[5] = (byte) (v >>> 16);
      byteBuf[6] = (byte) (v >>> 8);
      byteBuf[7] = (byte) (v >>> 0);
    } else {
      byteBuf[0] = (byte) (v >>> 0);
      byteBuf[1] = (byte) (v >>> 8);
      byteBuf[2] = (byte) (v >>> 16);
      byteBuf[3] = (byte) (v >>> 24);
      byteBuf[4] = (byte) (v >>> 32);
      byteBuf[5] = (byte) (v >>> 40);
      byteBuf[6] = (byte) (v >>> 48);
      byteBuf[7] = (byte) (v >>> 56);
    }
    // REMIND: Once 6277756 is fixed, we should do a bulk write of all 8
    // bytes here as we do in writeShort() and writeInt() for even better
    // performance.  For now, two bulk writes of 4 bytes each is still
    // faster than 8 individual write() calls (see 6347575 for details).
    write(byteBuf, 0, 4);
    write(byteBuf, 4, 4);
  }

  public void writeFloat(float v) throws IOException {
    writeInt(Float.floatToIntBits(v));
  }

  public void writeDouble(double v) throws IOException {
    writeLong(Double.doubleToLongBits(v));
  }

  public void writeBytes(String s) throws IOException {
    int len = s.length();
    for (int i = 0; i < len; i++) {
      write((byte) s.charAt(i));
    }
  }

  public void writeChars(String s) throws IOException {
    int len = s.length();

    byte[] b = new byte[len * 2];
    int boff = 0;
    if (byteOrder == ByteOrder.BIG_ENDIAN) {
      for (int i = 0; i < len; i++) {
        int v = s.charAt(i);
        b[boff++] = (byte) (v >>> 8);
        b[boff++] = (byte) (v >>> 0);
      }
    } else {
      for (int i = 0; i < len; i++) {
        int v = s.charAt(i);
        b[boff++] = (byte) (v >>> 0);
        b[boff++] = (byte) (v >>> 8);
      }
    }

    write(b, 0, len * 2);
  }

  public void writeUTF(String s) throws IOException {
    int strlen = s.length();
    int utflen = 0;
    char[] charr = new char[strlen];
    int c, boff = 0;

    s.getChars(0, strlen, charr, 0);

    for (int i = 0; i < strlen; i++) {
      c = charr[i];
      if ((c >= 0x0001) && (c <= 0x007F)) {
        utflen++;
      } else if (c > 0x07FF) {
        utflen += 3;
      } else {
        utflen += 2;
      }
    }

    if (utflen > 65535) {
      throw new UTFDataFormatException("utflen > 65536!");
    }

    byte[] b = new byte[utflen + 2];
    b[boff++] = (byte) ((utflen >>> 8) & 0xFF);
    b[boff++] = (byte) ((utflen >>> 0) & 0xFF);
    for (int i = 0; i < strlen; i++) {
      c = charr[i];
      if ((c >= 0x0001) && (c <= 0x007F)) {
        b[boff++] = (byte) c;
      } else if (c > 0x07FF) {
        b[boff++] = (byte) (0xE0 | ((c >> 12) & 0x0F));
        b[boff++] = (byte) (0x80 | ((c >> 6) & 0x3F));
        b[boff++] = (byte) (0x80 | ((c >> 0) & 0x3F));
      } else {
        b[boff++] = (byte) (0xC0 | ((c >> 6) & 0x1F));
        b[boff++] = (byte) (0x80 | ((c >> 0) & 0x3F));
      }
    }
    write(b, 0, utflen + 2);
  }

  public void writeShorts(short[] s, int off, int len) throws IOException {
    // Fix 4430357 - if off + len < 0, overflow occurred
    if (off < 0 || len < 0 || off + len > s.length || off + len < 0) {
      throw new IndexOutOfBoundsException
          ("off < 0 || len < 0 || off + len > s.length!");
    }

    byte[] b = new byte[len * 2];
    int boff = 0;
    if (byteOrder == ByteOrder.BIG_ENDIAN) {
      for (int i = 0; i < len; i++) {
        short v = s[off + i];
        b[boff++] = (byte) (v >>> 8);
        b[boff++] = (byte) (v >>> 0);
      }
    } else {
      for (int i = 0; i < len; i++) {
        short v = s[off + i];
        b[boff++] = (byte) (v >>> 0);
        b[boff++] = (byte) (v >>> 8);
      }
    }

    write(b, 0, len * 2);
  }

  public void writeChars(char[] c, int off, int len) throws IOException {
    // Fix 4430357 - if off + len < 0, overflow occurred
    if (off < 0 || len < 0 || off + len > c.length || off + len < 0) {
      throw new IndexOutOfBoundsException
          ("off < 0 || len < 0 || off + len > c.length!");
    }

    byte[] b = new byte[len * 2];
    int boff = 0;
    if (byteOrder == ByteOrder.BIG_ENDIAN) {
      for (int i = 0; i < len; i++) {
        char v = c[off + i];
        b[boff++] = (byte) (v >>> 8);
        b[boff++] = (byte) (v >>> 0);
      }
    } else {
      for (int i = 0; i < len; i++) {
        char v = c[off + i];
        b[boff++] = (byte) (v >>> 0);
        b[boff++] = (byte) (v >>> 8);
      }
    }

    write(b, 0, len * 2);
  }

  public void writeInts(int[] i, int off, int len) throws IOException {
    // Fix 4430357 - if off + len < 0, overflow occurred
    if (off < 0 || len < 0 || off + len > i.length || off + len < 0) {
      throw new IndexOutOfBoundsException
          ("off < 0 || len < 0 || off + len > i.length!");
    }

    byte[] b = new byte[len * 4];
    int boff = 0;
    if (byteOrder == ByteOrder.BIG_ENDIAN) {
      for (int j = 0; j < len; j++) {
        int v = i[off + j];
        b[boff++] = (byte) (v >>> 24);
        b[boff++] = (byte) (v >>> 16);
        b[boff++] = (byte) (v >>> 8);
        b[boff++] = (byte) (v >>> 0);
      }
    } else {
      for (int j = 0; j < len; j++) {
        int v = i[off + j];
        b[boff++] = (byte) (v >>> 0);
        b[boff++] = (byte) (v >>> 8);
        b[boff++] = (byte) (v >>> 16);
        b[boff++] = (byte) (v >>> 24);
      }
    }

    write(b, 0, len * 4);
  }

  public void writeLongs(long[] l, int off, int len) throws IOException {
    // Fix 4430357 - if off + len < 0, overflow occurred
    if (off < 0 || len < 0 || off + len > l.length || off + len < 0) {
      throw new IndexOutOfBoundsException
          ("off < 0 || len < 0 || off + len > l.length!");
    }

    byte[] b = new byte[len * 8];
    int boff = 0;
    if (byteOrder == ByteOrder.BIG_ENDIAN) {
      for (int i = 0; i < len; i++) {
        long v = l[off + i];
        b[boff++] = (byte) (v >>> 56);
        b[boff++] = (byte) (v >>> 48);
        b[boff++] = (byte) (v >>> 40);
        b[boff++] = (byte) (v >>> 32);
        b[boff++] = (byte) (v >>> 24);
        b[boff++] = (byte) (v >>> 16);
        b[boff++] = (byte) (v >>> 8);
        b[boff++] = (byte) (v >>> 0);
      }
    } else {
      for (int i = 0; i < len; i++) {
        long v = l[off + i];
        b[boff++] = (byte) (v >>> 0);
        b[boff++] = (byte) (v >>> 8);
        b[boff++] = (byte) (v >>> 16);
        b[boff++] = (byte) (v >>> 24);
        b[boff++] = (byte) (v >>> 32);
        b[boff++] = (byte) (v >>> 40);
        b[boff++] = (byte) (v >>> 48);
        b[boff++] = (byte) (v >>> 56);
      }
    }

    write(b, 0, len * 8);
  }

  public void writeFloats(float[] f, int off, int len) throws IOException {
    // Fix 4430357 - if off + len < 0, overflow occurred
    if (off < 0 || len < 0 || off + len > f.length || off + len < 0) {
      throw new IndexOutOfBoundsException
          ("off < 0 || len < 0 || off + len > f.length!");
    }

    byte[] b = new byte[len * 4];
    int boff = 0;
    if (byteOrder == ByteOrder.BIG_ENDIAN) {
      for (int i = 0; i < len; i++) {
        int v = Float.floatToIntBits(f[off + i]);
        b[boff++] = (byte) (v >>> 24);
        b[boff++] = (byte) (v >>> 16);
        b[boff++] = (byte) (v >>> 8);
        b[boff++] = (byte) (v >>> 0);
      }
    } else {
      for (int i = 0; i < len; i++) {
        int v = Float.floatToIntBits(f[off + i]);
        b[boff++] = (byte) (v >>> 0);
        b[boff++] = (byte) (v >>> 8);
        b[boff++] = (byte) (v >>> 16);
        b[boff++] = (byte) (v >>> 24);
      }
    }

    write(b, 0, len * 4);
  }

  public void writeDoubles(double[] d, int off, int len) throws IOException {
    // Fix 4430357 - if off + len < 0, overflow occurred
    if (off < 0 || len < 0 || off + len > d.length || off + len < 0) {
      throw new IndexOutOfBoundsException
          ("off < 0 || len < 0 || off + len > d.length!");
    }

    byte[] b = new byte[len * 8];
    int boff = 0;
    if (byteOrder == ByteOrder.BIG_ENDIAN) {
      for (int i = 0; i < len; i++) {
        long v = Double.doubleToLongBits(d[off + i]);
        b[boff++] = (byte) (v >>> 56);
        b[boff++] = (byte) (v >>> 48);
        b[boff++] = (byte) (v >>> 40);
        b[boff++] = (byte) (v >>> 32);
        b[boff++] = (byte) (v >>> 24);
        b[boff++] = (byte) (v >>> 16);
        b[boff++] = (byte) (v >>> 8);
        b[boff++] = (byte) (v >>> 0);
      }
    } else {
      for (int i = 0; i < len; i++) {
        long v = Double.doubleToLongBits(d[off + i]);
        b[boff++] = (byte) (v >>> 0);
        b[boff++] = (byte) (v >>> 8);
        b[boff++] = (byte) (v >>> 16);
        b[boff++] = (byte) (v >>> 24);
        b[boff++] = (byte) (v >>> 32);
        b[boff++] = (byte) (v >>> 40);
        b[boff++] = (byte) (v >>> 48);
        b[boff++] = (byte) (v >>> 56);
      }
    }

    write(b, 0, len * 8);
  }

  public void writeBit(int bit) throws IOException {
    writeBits((1L & bit), 1);
  }

  public void writeBits(long bits, int numBits) throws IOException {
    checkClosed();

    if (numBits < 0 || numBits > 64) {
      throw new IllegalArgumentException("Bad value for numBits!");
    }
    if (numBits == 0) {
      return;
    }

    // Prologue: deal with pre-existing bits

    // Bug 4499158, 4507868 - if we're at the beginning of the stream
    // and the bit offset is 0, there can't be any pre-existing bits
    if ((getStreamPosition() > 0) || (bitOffset > 0)) {
      int offset = bitOffset;  // read() will reset bitOffset
      int partialByte = read();
      if (partialByte != -1) {
        seek(getStreamPosition() - 1);
      } else {
        partialByte = 0;
      }

      if (numBits + offset < 8) {
        // Notch out the partial byte and drop in the new bits
        int shift = 8 - (offset + numBits);
        int mask = -1 >>> (32 - numBits);
        partialByte &= ~(mask << shift);  // Clear out old bits
        partialByte |= ((bits & mask) << shift); // Or in new ones
        write(partialByte);
        seek(getStreamPosition() - 1);
        bitOffset = offset + numBits;
        numBits = 0;  // Signal that we are done
      } else {
        // Fill out the partial byte and reduce numBits
        int num = 8 - offset;
        int mask = -1 >>> (32 - num);
        partialByte &= ~mask;  // Clear out bits
        partialByte |= ((bits >> (numBits - num)) & mask);
        // Note that bitOffset is already 0, so there is no risk
        // of this advancing to the next byte
        write(partialByte);
        numBits -= num;
      }
    }

    // Now write any whole bytes
    if (numBits > 7) {
      int extra = numBits % 8;
      for (int numBytes = numBits / 8; numBytes > 0; numBytes--) {
        int shift = (numBytes - 1) * 8 + extra;
        int value = (int) ((shift == 0)
            ? bits & 0xFF
            : (bits >> shift) & 0xFF);
        write(value);
      }
      numBits = extra;
    }

    // Epilogue: write out remaining partial byte, if any
    // Note that we may be at EOF, in which case we pad with 0,
    // or not, in which case we must preserve the existing bits
    if (numBits != 0) {
      // If we are not at the end of the file, read the current byte
      // If we are at the end of the file, initialize our byte to 0.
      int partialByte = 0;
      partialByte = read();
      if (partialByte != -1) {
        seek(getStreamPosition() - 1);
      }
      // Fix 4494976: writeBit(int) does not pad the remainder
      // of the current byte with 0s
      else { // EOF
        partialByte = 0;
      }

      int shift = 8 - numBits;
      int mask = -1 >>> (32 - numBits);
      partialByte &= ~(mask << shift);
      partialByte |= (bits & mask) << shift;
      // bitOffset is always already 0 when we get here.
      write(partialByte);
      seek(getStreamPosition() - 1);
      bitOffset = numBits;
    }
  }

  /**
   * If the bit offset is non-zero, forces the remaining bits
   * in the current byte to 0 and advances the stream position
   * by one.  This method should be called by subclasses at the
   * beginning of the <code>write(int)</code> and
   * <code>write(byte[], int, int)</code> methods.
   *
   * @throws IOException if an I/O error occurs.
   */
  protected final void flushBits() throws IOException {
    checkClosed();
    if (bitOffset != 0) {
      int offset = bitOffset;
      int partialByte = read(); // Sets bitOffset to 0
      if (partialByte < 0) {
        // Fix 4465683: When bitOffset is set
        // to something non-zero beyond EOF,
        // we should set that whole byte to
        // zero and write it to stream.
        partialByte = 0;
        bitOffset = 0;
      } else {
        seek(getStreamPosition() - 1);
        partialByte &= -1 << (8 - offset);
      }
      write(partialByte);
    }
  }

}
